package org.qpcr.community.project.web;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.hswebframework.ezorm.core.param.Term;
import org.hswebframework.ezorm.rdb.mapping.defaults.SaveResult;
import org.hswebframework.reactor.excel.ReactorExcel;
import org.hswebframework.web.api.crud.entity.QueryNoPagingOperation;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.authorization.annotation.QueryAction;
import org.hswebframework.web.authorization.annotation.Resource;
import org.hswebframework.web.authorization.annotation.SaveAction;
import org.hswebframework.web.bean.FastBeanCopier;
import org.hswebframework.web.crud.web.reactive.ReactiveServiceCrudController;
import org.hswebframework.web.exception.ValidationException;
import org.hswebframework.web.i18n.LocaleUtils;
import org.qpcr.community.io.excel.AbstractImporter;
import org.qpcr.community.io.file.FileManager;
import org.qpcr.community.project.model.entity.TemperatureControlEntity;
import org.qpcr.community.project.response.RelateProItemSeqDetailSync;
import org.qpcr.community.project.service.TemperatureControlService;
import org.qpcr.community.project.web.excel.ImportResult;
import org.qpcr.community.project.web.excel.TemperatureControlExcelFilterColumns;
import org.qpcr.community.project.web.excel.TemperatureControlExcelImporter;
import org.qpcr.community.project.web.excel.TemperatureControlExcelInfo;
import org.qpcr.community.web.response.ValidationResult;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.transaction.reactive.TransactionalOperator;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.concurrent.Queues;
import reactor.util.function.Tuples;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * 检测项管理
 */
@RestController
@RequestMapping( {"/project/temperature-control", "/project/temperature/control"})
@Authorize
@Resource(id = "temperature-control", name = "控温算法管理")
@Slf4j
@Tag(name = "控温算法管理接口")
public class TemperatureControlController implements
    ReactiveServiceCrudController<TemperatureControlEntity, String> {
    @Getter
    private final TemperatureControlService service;
    private final TemperatureControlExcelFilterColumns filterColumns;
    private final FileManager fileManager;
    private final WebClient webClient;
    private final TransactionalOperator transactionalOperator;

    @SuppressWarnings("all")
    public TemperatureControlController(TemperatureControlService service,
                                        TemperatureControlExcelFilterColumns filterColumns,
                                        FileManager fileManager,
                                        WebClient.Builder builder,
                                        TransactionalOperator transactionalOperator) {
        this.service = service;
        this.filterColumns = filterColumns;
        this.fileManager = fileManager;
        this.webClient = builder.build();
        this.transactionalOperator = transactionalOperator;
    }
    DataBufferFactory bufferFactory = new DefaultDataBufferFactory();

    @GetMapping("/template.{format}")
    @QueryAction
    @Operation(summary = "下载控温算法导入模版")
    public Mono<Void> downloadExportTemplate(ServerHttpResponse response,
                                             @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
        response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
                "attachment; filename=".concat(URLEncoder.encode("控温算法导入模版." + format, StandardCharsets.UTF_8
                        .displayName())));
        return
            Mono.just(new TemperatureControlEntity())
            .map(tp1 -> TemperatureControlExcelInfo.getTemplateHeaderMapping(filterColumns))
            .defaultIfEmpty(TemperatureControlExcelInfo.getTemplateHeaderMapping(filterColumns))
            .flatMapMany(headers ->
                             ReactorExcel.<TemperatureControlExcelInfo>writer(format)
                                 .headers(headers)
                                 .writeBuffer(Flux.empty()))
            .doOnError(err -> log.error(err.getMessage(), err))
            .map(bufferFactory::wrap)
            .as(response::writeWith);
    }

    @PostMapping("/code/_validate/{code}")
    @Authorize(merge = false)
    @Operation(summary = "控温算法编号验证")
    public Mono<ValidationResult> ItemCodeValidate(@PathVariable(required = true) String code) {

        return LocaleUtils
            .currentReactive()
            .flatMap(locale -> {
                return service.findByItemCode(code)
                              .map(i -> ValidationResult.error(LocaleUtils.resolveMessage(
                                  "error.code_already_exist",
                                  locale,
                                  "控温算法编码" + code + "已存在", code))
                              );
            })
            .defaultIfEmpty(ValidationResult.success())
            .onErrorResume(ValidationException.class,
                           e -> e.getLocalizedMessageReactive().map(ValidationResult::error));
    }
    @PostMapping("/code/_validate/{code}/{id}")
    @Authorize(merge = false)
    @Operation(summary = "根据ID控温算法编号验证")
    public Mono<ValidationResult> ItemCodeValidate(@PathVariable(required = true) String code,@PathVariable(required = true) String id) {

        return LocaleUtils
            .currentReactive()
            .flatMap(locale -> {
                return service.findByItemCode(code,id)
                              .map(i -> ValidationResult.error(LocaleUtils.resolveMessage(
                                  "error.code_already_exist",
                                  locale,
                                  "控温算法编码" + code + "已存在", code))
                              );
            })
            .defaultIfEmpty(ValidationResult.success())
            .onErrorResume(ValidationException.class,
                           e -> e.getLocalizedMessageReactive().map(ValidationResult::error));
    }


    /**
     * 导出检测项数据
     *
     * @param response  response
     * @param parameter parameter
     * @param format    format
     * @return Mono<Void>
     * @throws IOException IOException
     */
    @PostMapping("/export.{format}")
    @QueryAction
    @QueryNoPagingOperation(summary = "导控温算法数据")
    public Mono<Void> export(ServerHttpResponse response,
                             @RequestBody QueryParamEntity parameter,
                             @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
        response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
                "attachment; filename=".concat(URLEncoder.encode("控温算法." + format, StandardCharsets.UTF_8
                        .displayName())));
        parameter.setPaging(false);
        return Mono.just(
                    //检测项
                    service.query(parameter)
                )
                .map(tp1 -> Tuples
                    .of(
                        //表头
                        TemperatureControlExcelInfo.getExportHeaderMapping(filterColumns),
                        Collections.emptyList()
                    ))
                .defaultIfEmpty(Tuples.of(TemperatureControlExcelInfo.getExportHeaderMapping(filterColumns),
                        Collections.emptyList()))
                .flatMapMany(headerAndConfigKey -> ReactorExcel
                        .<TemperatureControlExcelInfo>writer(format)
                        .headers(headerAndConfigKey.getT1())
                        .converter(TemperatureControlExcelInfo::toMap)
                        .writeBuffer(service
                                        .query(parameter)
                                        .flatMap(entity -> Mono.just(FastBeanCopier.copy(entity, new TemperatureControlExcelInfo())))
                                        .buffer(200)
                                        .flatMap(Flux::fromIterable)
                                , 512 * 1024))//缓冲512k
                .doOnError(err -> log.error(err.getMessage(), err))
                .map(bufferFactory::wrap)
                .as(response::writeWith);
    }

    @GetMapping(value = "/import/_withlog", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @SaveAction
    @Operation(summary = "导入控温算法数据，并提供日志下载")
    public Flux<ImportResult> doBatchImportWithLog(
            @RequestParam @Parameter(description = "文件地址,支持csv,xlsx文件格式") String fileUrl,
            @RequestParam(defaultValue = "32") @Parameter int speed
    ) {
        return Authentication
                .currentReactive()
                .flatMapMany(auth -> Mono.zip(
                                //检测项
                                service.createQuery().fetch().collectList(),
                                Mono.just(new TemperatureControlEntity())
                        )
                        .map(tp2 -> new TemperatureControlExcelImporter(fileManager, webClient, tp2.getT1(), auth))
                        .flatMapMany(importer -> importer
                                .doImport(fileUrl)
                                .groupBy(
                                        result -> result.isSuccess() && result.getType() == AbstractImporter.ImportResultType.data,
                                        Integer.MAX_VALUE
                                )
                                .flatMap(group -> {
                                    // 处理导入成功的检测项
                                    if (group.key()) {
                                        return group
                                                .map(result -> result.getData().getTemperatureControlInstance())
                                                .as(flux -> handleImportDevice(flux, speed));
                                    }
                                    // 返回错误信息和导入结果详情文件地址
                                    return group
                                            .map(result -> {
                                                ImportResult response = new ImportResult();
                                                response.setSuccess(result.isSuccess());
                                                if (StringUtils.hasText(result.getMessage())) {
                                                    response.setMessage(String.format("第%d行：%s", result.getRow(), result.getMessage()));
                                                }
                                                response.setDetailFile(result.getDetailFile());
                                                return response;
                                            });
                                })
                        )
                );
    }
    private Flux<ImportResult> handleImportDevice(Flux<TemperatureControlEntity> flux,
                                                  int speed) {
        return flux
                .buffer(100)//每100条数据保存一次
                .map(Flux::fromIterable)
                .flatMap(buffer -> Mono
                                .zip(buffer
                                    .as(service::save)
                                    .flatMap(Mono::just),
                                    Mono.just(SaveResult.of(0, 0))
                                )
                                .as(transactionalOperator::transactional),
                                Math.min(speed, Queues.XS_BUFFER_SIZE)
                )
                .map(res -> ImportResult.success(res.getT1()))
                .onErrorResume(err -> Mono.just(ImportResult.error(err)));
    }

    @GetMapping("/sync")
    @QueryAction
    @Operation(summary = "设备同步控温算法")
    @Authorize(ignore = true)
    public Flux<TemperatureControlEntity> sync(Long lastUpdateTime) {
        QueryParamEntity param=new QueryParamEntity();
        if(lastUpdateTime!=null){
            param.setWhere(" modify_time >"+lastUpdateTime);
        }
        param.noPaging();

        return this.service.query(param);
    }

    @GetMapping("list-all")
    @QueryAction
    @Operation(summary = "获取所有控温算法")
    @Authorize(ignore = true)
    public Mono<List<TemperatureControlEntity>> listAll() {
        return this.service.createQuery().fetch().collectList();
    }
}
